#include <sys/types.h>
#include <sys/socket.h>

#include <iostream>
#include "cmdListener.h"
#include "GamestateWriter.h"

extern int solveProtobuff(const tilemessage::GameState& gs);

int *gsox;
int numSox;

static int opensock(short port)
{
    int sock;
    int rc,y;
    int optval=1;
    struct sockaddr_in *server_addr=(struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
    sock = socket(AF_INET, SOCK_STREAM,  IPPROTO_TCP);
    if (sock < 0)  perror("socket"), exit(-1);
    if((rc=setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(optval)))<0)
        perror("REUSEADDR ERROR"), exit(-1);
#ifdef SO_REUSEPORT
    if((rc=setsockopt(sock,SOL_SOCKET,SO_REUSEPORT,&optval,sizeof(optval)))<0)
        perror("REUSEPORT ERROR"), exit(-1);
#endif
    bzero((char *) server_addr, sizeof(struct sockaddr_in));
    server_addr->sin_family = PF_INET;
    server_addr->sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr->sin_port = htons(port);

    for(y=4;y>0;y--) {
        if(!bind(sock, (struct sockaddr *) server_addr, sizeof(struct sockaddr_in)))
            break;
        if(errno != EADDRINUSE) perror("bind"), exit(-1);
        fprintf(stderr,"Retrying local port ..");
        sleep(1);
    }
    if (listen(sock, 5) < 0) perror("listen"), exit(-1);
    return sock;
}

static int acceptsock(int sock)
{
    socklen_t size;
    int newsock;
    struct sockaddr_in client_addr;

    newsock = accept(sock, (struct sockaddr *) &client_addr, &size);
    if (newsock < 0) perror("accept"), exit(-1);
    return newsock;
}

void *echoCmd(void *arg)
{
    int mysocfd = (long)arg;
    ssize_t t;

    tilemessage::GameState gs;
    GamestateWriter::GamestateWriter gsw;
    if(false) {
        char *buff = (char *)malloc(READBUFFER_SIZE);
        t=read(mysocfd,buff,READBUFFER_SIZE);
        gs.ParseFromArray(buff,t);
        free(buff);
    }
    else {
        if(!gs.ParseFromFileDescriptor(mysocfd)) {
            std::cerr << "Failed to parse input stream." << std::endl;
            return (void *)NULL;
        }
    }
    solveProtobuff(gs);

    gsw.showState(gs);
    return (void *)NULL;
}

//static char *sockAddrPort(const int sock)
//{
//    char *ans=(char *)malloc(25);
//    struct sockaddr *name=(struct sockaddr *)malloc(sizeof(struct sockaddr));
//    socklen_t *namelen=(socklen_t *)malloc(sizeof(socklen_t));
//    char *nameShadow=(char *)name;
//    *namelen=sizeof(struct sockaddr);
//    getsockname(sock,name,namelen);
//    sprintf(ans,"%s/%u",inet_ntoa(*(struct in_addr *)(nameShadow+4)),(*(nameShadow+2)<<8)+*(nameShadow+3));
//    return ans;
//}
//
//static char *peerAddrPort(const int sock)
//{
//    char *ans=(char *)malloc(25);
//    struct sockaddr *name=(struct sockaddr *)malloc(sizeof(struct sockaddr));
//    socklen_t *namelen=(socklen_t *)malloc(sizeof(socklen_t));
//    char *nameShadow=(char *)name;
//    *namelen=sizeof(struct sockaddr);
//    getpeername(sock,name,namelen);
//    sprintf(ans,"%s/%u",inet_ntoa(*(struct in_addr *)(nameShadow+4)),(*(nameShadow+2)<<8)+*(nameShadow+3));
//    return ans;
//}

static void *cmdListener(void *dummy)
{
    pthread_attr_t *attr;
    pthread_t *tid;
    int newsock, sock, rc;
    long x = (long)dummy;
    int port =x;
    fprintf(stderr,"cmdListener: listening on port %d\n",port);
#define ONEREADER
#ifdef ONEREADER
    sock=opensock(port);
#endif
    for(;;) {
        attr=(pthread_attr_t *)malloc(sizeof(pthread_attr_t));
        pthread_attr_init(attr);
        pthread_attr_setscope(attr, PTHREAD_SCOPE_PROCESS); // or _SYSTEM
        pthread_attr_setschedpolicy(attr, SCHED_RR); // OR _FIFO
        tid=(pthread_t *)malloc(sizeof(pthread_t));
#ifndef ONEREADER
        sock=opensock(port);
        newsock=acceptsock(sock);
        echoCmd((void *)newsock);
#else
        newsock=acceptsock(sock);
        rc=pthread_create(tid, attr, echoCmd, (void *)newsock);
#endif
        sleep(1);
        fprintf(stderr,"again..\n");
    }
    return (void *)NULL;
}

pthread_t startCmdListener(int port) // sub makes thread
{
    pthread_t tid;
    pthread_attr_t attr;
    int rc;
    errno=0;
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_PROCESS);
    pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
    rc = pthread_create(&tid, NULL, cmdListener, (void *)port);
    if(rc!=0) {
        char *errmsg=(char *)malloc(127);
        fprintf(stderr,"StartCmdListener: rc=%d, EAGAIN=%d\n",rc,EAGAIN);
        strerror_r(errno,errmsg,127);
        fprintf(stderr,"startCmdListener: pthread_create errno=%d.  Error is %s\n",errno,errmsg);
        strerror_r(rc,errmsg,127);
        fprintf(stderr,"startCmdListener: pthread_create rc=%d.  Error is %s\n",rc,errmsg);
    }
    return tid;
}
